// Copyright (c) 2022 NetEase, Inc. All rights reserved.
// Use of this source code is governed by a MIT license that can be
// found in the LICENSE file.

package com.faceunity.nama.utils;

import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class FuDeviceUtils {

  public static final String TAG = "FuDeviceUtils";

  public static final int DEVICE_LEVEL_HIGH = 2;
  public static final int DEVICE_LEVEL_MID = 1;
  public static final int DEVICE_LEVEL_LOW = 0;

  /**
   * The default return value of any method in this class when an error occurs or when processing
   * fails (Currently set to -1). Use this to check if the information about the device in question
   * was successfully obtained.
   */
  public static final int DEVICEINFO_UNKNOWN = -1;

  private static final FileFilter CPU_FILTER =
      new FileFilter() {
        @Override
        public boolean accept(File pathname) {
          String path = pathname.getName();
          //regex is slow, so checking char by char.
          if (path.startsWith("cpu")) {
            for (int i = 3; i < path.length(); i++) {
              if (!Character.isDigit(path.charAt(i))) {
                return false;
              }
            }
            return true;
          }
          return false;
        }
      };

  /**
   * Calculates the total RAM of the device through Android API or /proc/meminfo.
   *
   * @param c - Context object for current running activity.
   * @return Total RAM that the device has, or DEVICEINFO_UNKNOWN = -1 in the event of an error.
   */
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  public static long getTotalMemory(Context c) {
    // memInfo.totalMem not supported in pre-Jelly Bean APIs.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
      ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
      ActivityManager am = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
      am.getMemoryInfo(memInfo);
      if (memInfo != null) {
        return memInfo.totalMem;
      } else {
        return DEVICEINFO_UNKNOWN;
      }
    } else {
      long totalMem = DEVICEINFO_UNKNOWN;
      try {
        FileInputStream stream = new FileInputStream("/proc/meminfo");
        try {
          totalMem = parseFileForValue("MemTotal", stream);
          totalMem *= 1024;
        } finally {
          stream.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
      return totalMem;
    }
  }

  /**
   * Method for reading the clock speed of a CPU core on the device. Will read from either {@code
   * /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq} or {@code /proc/cpuinfo}.
   *
   * @return Clock speed of a core on the device, or -1 in the event of an error.
   */
  public static int getCPUMaxFreqKHz() {
    int maxFreq = DEVICEINFO_UNKNOWN;
    try {
      for (int i = 0; i < getNumberOfCPUCores(); i++) {
        String filename = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq";
        File cpuInfoMaxFreqFile = new File(filename);
        if (cpuInfoMaxFreqFile.exists() && cpuInfoMaxFreqFile.canRead()) {
          byte[] buffer = new byte[128];
          FileInputStream stream = new FileInputStream(cpuInfoMaxFreqFile);
          try {
            stream.read(buffer);
            int endIndex = 0;
            //Trim the first number out of the byte buffer.
            while (Character.isDigit(buffer[endIndex]) && endIndex < buffer.length) {
              endIndex++;
            }
            String str = new String(buffer, 0, endIndex);
            Integer freqBound = Integer.parseInt(str);
            if (freqBound > maxFreq) {
              maxFreq = freqBound;
            }
          } catch (NumberFormatException e) {
            //Fall through and use /proc/cpuinfo.
          } finally {
            stream.close();
          }
        }
      }
      if (maxFreq == DEVICEINFO_UNKNOWN) {
        FileInputStream stream = new FileInputStream("/proc/cpuinfo");
        try {
          int freqBound = parseFileForValue("cpu MHz", stream);
          freqBound *= 1024; //MHz -> kHz
          if (freqBound > maxFreq) maxFreq = freqBound;
        } finally {
          stream.close();
        }
      }
    } catch (IOException e) {
      maxFreq = DEVICEINFO_UNKNOWN; //Fall through and return unknown.
    }
    return maxFreq;
  }

  /**
   * Reads the number of CPU cores from the first available information from {@code
   * /sys/devices/system/cpu/possible}, {@code /sys/devices/system/cpu/present}, then {@code
   * /sys/devices/system/cpu/}.
   *
   * @return Number of CPU cores in the phone, or DEVICEINFO_UKNOWN = -1 in the event of an error.
   */
  public static int getNumberOfCPUCores() {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
      // Gingerbread doesn't support giving a single application access to both cores, but a
      // handful of devices (Atrix 4G and Droid X2 for example) were released with a dual-core
      // chipset and Gingerbread; that can let an app in the background run without impacting
      // the foreground application. But for our purposes, it makes them single core.
      return 1;
    }
    int cores;
    try {
      cores = getCoresFromFileInfo("/sys/devices/system/cpu/possible");
      if (cores == DEVICEINFO_UNKNOWN) {
        cores = getCoresFromFileInfo("/sys/devices/system/cpu/present");
      }
      if (cores == DEVICEINFO_UNKNOWN) {
        cores = new File("/sys/devices/system/cpu/").listFiles(CPU_FILTER).length;
      }
    } catch (SecurityException e) {
      cores = DEVICEINFO_UNKNOWN;
    } catch (NullPointerException e) {
      cores = DEVICEINFO_UNKNOWN;
    }
    return cores;
  }

  /**
   * Tries to read file contents from the file location to determine the number of cores on device.
   *
   * @param fileLocation The location of the file with CPU information
   * @return Number of CPU cores in the phone, or DEVICEINFO_UKNOWN = -1 in the event of an error.
   */
  private static int getCoresFromFileInfo(String fileLocation) {
    InputStream is = null;
    try {
      is = new FileInputStream(fileLocation);
      BufferedReader buf = new BufferedReader(new InputStreamReader(is));
      String fileContents = buf.readLine();
      buf.close();
      return getCoresFromFileString(fileContents);
    } catch (IOException e) {
      return DEVICEINFO_UNKNOWN;
    } finally {
      if (is != null) {
        try {
          is.close();
        } catch (IOException e) {
          // Do nothing.
        }
      }
    }
  }

  /**
   * Converts from a CPU core information format to number of cores.
   *
   * @param str The CPU core information string, in the format of "0-N"
   * @return The number of cores represented by this string
   */
  private static int getCoresFromFileString(String str) {
    if (str == null || !str.matches("0-[\\d]+$")) {
      return DEVICEINFO_UNKNOWN;
    }
    return Integer.valueOf(str.substring(2)) + 1;
  }

  /**
   * Helper method for reading values from system files, using a minimised buffer.
   *
   * @param textToMatch - Text in the system files to read for.
   * @param stream - FileInputStream of the system file being read from.
   * @return A numerical value following textToMatch in specified the system file. -1 in the event
   *     of a failure.
   */
  private static int parseFileForValue(String textToMatch, FileInputStream stream) {
    byte[] buffer = new byte[1024];
    try {
      int length = stream.read(buffer);
      for (int i = 0; i < length; i++) {
        if (buffer[i] == '\n' || i == 0) {
          if (buffer[i] == '\n') i++;
          for (int j = i; j < length; j++) {
            int textIndex = j - i;
            //Text doesn't match query at some point.
            if (buffer[j] != textToMatch.charAt(textIndex)) {
              break;
            }
            //Text matches query here.
            if (textIndex == textToMatch.length() - 1) {
              return extractValue(buffer, j);
            }
          }
        }
      }
    } catch (IOException e) {
      //Ignore any exceptions and fall through to return unknown value.
    } catch (NumberFormatException e) {
    }
    return DEVICEINFO_UNKNOWN;
  }

  /**
   * Helper method used by {@link #parseFileForValue(String, FileInputStream) parseFileForValue}.
   * Parses the next available number after the match in the file being read and returns it as an
   * integer.
   *
   * @param index - The index in the buffer array to begin looking.
   * @return The next number on that line in the buffer, returned as an int. Returns
   *     DEVICEINFO_UNKNOWN = -1 in the event that no more numbers exist on the same line.
   */
  private static int extractValue(byte[] buffer, int index) {
    while (index < buffer.length && buffer[index] != '\n') {
      if (Character.isDigit(buffer[index])) {
        int start = index;
        index++;
        while (index < buffer.length && Character.isDigit(buffer[index])) {
          index++;
        }
        String str = new String(buffer, 0, start, index - start);
        return Integer.parseInt(str);
      }
      index++;
    }
    return DEVICEINFO_UNKNOWN;
  }

  /**
   * 获取当前剩余内存(ram)
   *
   * @param context
   * @return
   */
  public static long getAvailMemory(Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
    am.getMemoryInfo(mi);
    return mi.availMem;
  }

  /**
   * 获取厂商信息
   *
   * @return
   */
  public static String getBrand() {
    return Build.BRAND;
  }

  /**
   * 获取手机机型
   *
   * @return
   */
  public static String getModel() {
    return Build.MODEL;
  }

  /**
   * 获取硬件信息(cpu型号)
   *
   * @return
   */
  public static String getHardWare() {
    try {
      FileReader fr = new FileReader("/proc/cpuinfo");
      BufferedReader br = new BufferedReader(fr);
      String text;
      String last = "";
      while ((text = br.readLine()) != null) {
        last = text;
      }
      //一般机型的cpu型号都会在cpuinfo文件的最后一行
      if (last.contains("Hardware")) {
        String[] hardWare = last.split(":\\s+", 2);
        return hardWare[1];
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return Build.HARDWARE;
  }

  /**
   * Level judgement based on current memory and CPU.
   *
   * @param context - Context object.
   * @return
   */
  public static int judgeDeviceLevel(Context context) {
    int level;
    //有一些设备不符合下述的判断规则，则走一个机型判断模式
    int specialDevice = judgeDeviceLevelInDeviceName();
    if (specialDevice >= 0) return specialDevice;

    int ramLevel = judgeMemory(context);
    int cpuLevel = judgeCPU();
    if (ramLevel == 0 || ramLevel == 1 || cpuLevel == 0) {
      level = DEVICE_LEVEL_LOW;
    } else {
      if (cpuLevel > 1) {
        level = DEVICE_LEVEL_HIGH;
      } else {
        level = DEVICE_LEVEL_MID;
      }
    }
    Log.d(TAG, "DeviceLevel: " + level);
    return level;
  }

  /**
   * -1 不是特定的高低端机型
   *
   * @return
   */
  private static int judgeDeviceLevelInDeviceName() {
    String currentDeviceName = getDeviceName();
    for (String deviceName : upscaleDevice) {
      if (deviceName.equals(currentDeviceName)) {
        return DEVICE_LEVEL_HIGH;
      }
    }

    for (String deviceName : middleDevice) {
      if (deviceName.equals(currentDeviceName)) {
        return DEVICE_LEVEL_MID;
      }
    }

    for (String deviceName : lowDevice) {
      if (deviceName.equals(currentDeviceName)) {
        return DEVICE_LEVEL_LOW;
      }
    }
    return -1;
  }

  public static final String[] upscaleDevice = {"vivo X6S A", "MHA-AL00", "VKY-AL00", "V1838A"};
  public static final String[] lowDevice = {};
  public static final String[] middleDevice = {
    "OPPO R11s", "PAR-AL00", "MI 8 Lite", "ONEPLUS A6000", "PRO 6", "PRO 7 Plus"
  };

  /**
   * 评定内存的等级.
   *
   * @return
   */
  private static int judgeMemory(Context context) {
    long ramMB = getTotalMemory(context) / (1024 * 1024);
    int level = -1;
    if (ramMB <= 2000) { //2G或以下的最低档
      level = 0;
    } else if (ramMB <= 3000) { //2-3G
      level = 1;
    } else if (ramMB <= 4000) { //4G档 2018主流中端机
      level = 2;
    } else if (ramMB <= 6000) { //6G档 高端机
      level = 3;
    } else { //6G以上 旗舰机配置
      level = 4;
    }
    return level;
  }

  /**
   * 评定CPU等级.（按频率和厂商型号综合判断）
   *
   * @return
   */
  private static int judgeCPU() {
    int level = 0;
    String cpuName = getHardWare();
    int freqMHz = getCPUMaxFreqKHz() / 1024;

    //一个不符合下述规律的高级白名单
    //如果可以获取到CPU型号名称 -> 根据不同的名称走不同判定策略
    if (!TextUtils.isEmpty(cpuName)) {
      if (cpuName.contains("qcom") || cpuName.contains("Qualcomm")) { //高通骁龙
        return judgeQualcommCPU(cpuName, freqMHz);
      } else if (cpuName.contains("hi") || cpuName.contains("kirin")) { //海思麒麟
        return judgeSkinCPU(cpuName, freqMHz);
      } else if (cpuName.contains("MT")) { //联发科
        return judgeMTCPU(cpuName, freqMHz);
      }
    }

    //cpu型号无法获取的普通规则
    if (freqMHz <= 1600) { //1.5G 低端
      level = 0;
    } else if (freqMHz <= 1950) { //2GHz 低中端
      level = 1;
    } else if (freqMHz <= 2500) { //2.2 2.3g 中高端
      level = 2;
    } else { //高端
      level = 3;
    }
    return level;
  }

  /**
   * 联发科芯片等级判定
   *
   * @return
   */
  private static int judgeMTCPU(String cpuName, int freqMHz) {
    //P60之前的全是低端机 MT6771V/C
    int level = 0;
    int mtCPUVersion = getMTCPUVersion(cpuName);
    if (mtCPUVersion == -1) {
      //读取不出version 按照一个比较严格的方式来筛选出高端机
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else if (freqMHz <= 2200) { //2GHz 低中端
        level = 1;
      } else if (freqMHz <= 2700) { //2.2 2.3g 中高端
        level = 2;
      } else { //高端
        level = 3;
      }
    } else if (mtCPUVersion < 6771) {
      //均为中低端机
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else { //2GHz 中端
        level = 1;
      }
    } else {
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else if (freqMHz <= 1900) { //2GHz 低中端
        level = 1;
      } else if (freqMHz <= 2500) { //2.2 2.3g 中高端
        level = 2;
      } else { //高端
        level = 3;
      }
    }

    return level;
  }

  /**
   * 通过联发科CPU型号定义 -> 获取cpu version
   *
   * @param cpuName
   * @return
   */
  private static int getMTCPUVersion(String cpuName) {
    //截取MT后面的四位数字
    int cpuVersion = -1;
    if (cpuName.length() > 5) {
      String cpuVersionStr = cpuName.substring(2, 6);
      try {
        cpuVersion = Integer.valueOf(cpuVersionStr);
      } catch (NumberFormatException exception) {
        exception.printStackTrace();
      }
    }

    return cpuVersion;
  }

  /**
   * 高通骁龙芯片等级判定
   *
   * @return
   */
  private static int judgeQualcommCPU(String cpuName, int freqMHz) {
    int level = 0;
    //xxxx inc MSM8937 比较老的芯片
    //7 8 xxx inc SDM710
    if (cpuName.contains("MSM")) {
      //老芯片
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else { //2GHz 低中端
        level = 1;
      }
    } else {
      //新的芯片
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else if (freqMHz <= 2000) { //2GHz 低中端
        level = 1;
      } else if (freqMHz <= 2500) { //2.2 2.3g 中高端
        level = 2;
      } else { //高端
        level = 3;
      }
    }

    return level;
  }

  /**
   * 麒麟芯片等级判定
   *
   * @param freqMHz
   * @return
   */
  private static int judgeSkinCPU(String cpuName, int freqMHz) {
    //型号 -> kirin710之后 & 最高核心频率
    int level = 0;
    if (cpuName.startsWith("hi")) {
      //这个是海思的芯片中低端
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else if (freqMHz <= 2000) { //2GHz 低中端
        level = 1;
      }
    } else {
      //这个是海思麒麟的芯片
      if (freqMHz <= 1600) { //1.5G 低端
        level = 0;
      } else if (freqMHz <= 2000) { //2GHz 低中端
        level = 1;
      } else if (freqMHz <= 2500) { //2.2 2.3g 中高端
        level = 2;
      } else { //高端
        level = 3;
      }
    }

    return level;
  }

  public static final String Nexus_6P = "Nexus 6P";

  /**
   * 获取设备名
   *
   * @return
   */
  public static String getDeviceName() {
    String deviceName = "";
    if (Build.MODEL != null) deviceName = Build.MODEL;
    Log.e(TAG, "deviceName: " + deviceName);
    return deviceName;
  }
}
